15. The "synchronized" Keyword

The synchronized Keyword

In this section, you will learn how to use the synchronized keyword in Java.

ND079 JPND C2 L05 A16 Synchronized Keyword

How to Use the synchronized Keyword

The best way to synchronize is to use high-level built-in tools like the synchronized Collections wrappers, or data structures in the java.util.concurrent package.

When those options aren't available, you can use the synchronized keyword for low-level synchronization control.

Here's an example using the voting app code from the previous section:

public final class VotingApp {
  private final Map<String, Integer> votes = new HashMap<>();

  public void castVote(String performer) {
    synchronized (this) {
      Integer count = votes.get(performer);
      if (count == null) {
        votes.put(performer, 1);
      } else {
        votes.put(performer, count + 1);
      }
    }
  }
}

The thing in parentheses after the synchronized keyword is the lock object. When a thread enters the code block, it takes ownership of the lock. Only one thread at a time can own the lock at a time, so only one thread is allowed to be executing code inside the synchronized block at a given time.

Lock Objects

Any object can be used as the lock. For example, you could use the votes map as the lock object:

public void castVote(String performer) {
    synchronized (votes) {
      ...
    }
  }
}

Or, you could create a completely new object just to serve the purpose of the lock:

private final Object lock = "SpecialLock";
public void castVote(String performer) {
    synchronized (lock) {
      ...
    }
  }
}

If you decide to use the this keyword, the lock object is the current instance of the class. If you're using this to lock the entire method, you can use this trick instead:

public final class VotingApp {
  private final Map<String, Integer> votes = new HashMap<>();

  public synchronized void castVote(String performer) {
    Integer count = votes.get(performer);
    if (count == null) {
      votes.put(performer, 1);
    } else {
      votes.put(performer, count + 1);
    }
  }
}

This code does the same thing, but is a little nicer to look at.

Demo: Thread-Safe Singleton Pattern

In this demo, we'll take a look at how to make the singleton design pattern thread-safe.

ND079 JPND C2 L05 A17 Demo Synchronized Keyword

Code from the Demo

import java.util.Objects;

public final class Database {
    private static Database database;

    private Database() {}

    public static Database getInstance() {
        if (database == null) {
            synchronized (Database.class) {
                if (database == null) {
                    database = new Database();
                    database.connect("/usr/local/data/users.db");
                }
            }
        }
        return database;
    }

    // Connects to the remote database.
    private void connect(String url) {
        Objects.requireNonNull(url);
    }

    public static void main(String[] args) {
        Database a = Database.getInstance();
        Database b = Database.getInstance();

        System.out.println(a == b);
    }
}

Looking at the getInstance() method from the demo code, why is it important to have a second null check inside the synchronized block?

In other words, what's wrong with this code?

public static Database getInstance() { 
        if (database == null) {
            synchronized (Database.class) {
                database = new Database();
                database.connect("/usr/local/data/users.db");
            }
        }
        return database;
    }
SOLUTION: One thread could get past the first `null` check while the another thread is already inside the `synchronized` block.